using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Drawing.Design;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.Configuration;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;
using Moxiecode.TinyMCE.Settings;
using Moxiecode.TinyMCE.Configuration;

namespace Moxiecode.TinyMCE.Web.Controls 
{
    [ToolboxData("<{0}:TinyMCE runat=\"server\"></{0}:TinyMCE>")]
    public class TinyMCE : BaseTinyMCE
    {
        #region Shipped Plugins

        protected static readonly List<Plugin> ShippedPlugins = new List<Plugin>()
        {
            new Plugin("autosave"),
            new Plugin("bbcode"),
            new Plugin("compat2x"),
            new Plugin("contextmenu"),
            new Plugin("inlinepopups"),
            new Plugin("noneditable"),
            new Plugin("safari"),

            new Plugin("table", new List<string>() { "tablecontrols" }),
            new Plugin("pagebreak", new List<string>() { "pagebreak" }),
            new Plugin("style", new List<string>() { "styleprops" }),
            new Plugin("layer", new List<string>() { "moveforward", "movebackward", "absolute", "insertlayer" }),
            new Plugin("save", new List<string>() { "save", "cancel" }),
            new Plugin("advhr", new List<string>() { "advhr" }),
            new Plugin("advimage", new List<string>() { "image" }),
            new Plugin("advlink", new List<string>() { "link" }),
            new Plugin("emotions", new List<string>() { "emotions" }),
            new Plugin("iespell", new List<string>() { "iespell" }),
            new Plugin("spellchecker", new List<string>() { "spellchecker" }),
            new Plugin("insertdatetime", new List<string>() { "insertdate", "inserttime" }),
            new Plugin("preview", new List<string>() { "preview" }),
            new Plugin("media", new List<string>() { "media" }),
            new Plugin("searchreplace", new List<string>() { "search", "replace" }),
            new Plugin("print", new List<string>() { "print" }),
            new Plugin("paste", new List<string>() { "pastetext", "pasteword", "selectall" }),
            new Plugin("example", new List<string>() { "example" }),
            new Plugin("directionality", new List<string>() { "ltr", "rtl" }),
            new Plugin("fullpage", new List<string>() { "fullpage" }),
            new Plugin("fullscreen", new List<string>() { "fullscreen" }),
            new Plugin("visualchars", new List<string>() { "visualchars" }),
            new Plugin("nonbreaking", new List<string>() { "nonbreaking" }),
            new Plugin("xhtmlxtras", new List<string>() { "cite", "acronym", "abbr", "del", "ins", "attribs" }),
            new Plugin("template", new List<string>() { "template" })
        };

        #endregion

        #region Constructors

        public TinyMCE() : base() { }

        #endregion

        #region Methods

        #region Rendering

        protected override void PrepRenderState()
        {
            List<Plugin> plugins = Plugins;
            Plugin spellCheck = ShippedPlugins.Find(p => p.Name == "spellchecker");
            if (plugins != null && plugins.Count > 0 && plugins.Contains(spellCheck) && String.IsNullOrEmpty(SpellCheckerUrl))
                SpellCheckerUrl = VirtualPathUtility.ToAbsolute("~/") + "TinyMCE.ashx?module=SpellChecker";

            HandleCssSelectors();

            base.PrepRenderState();
        }

        private void HandleCssSelectors()
        {
            if (!String.IsNullOrEmpty(CssClassSelector))
            {
                Control[] targs = TargetControls;
                var selected = targs.OfType<TextBox>().Where(t => t.CssClass.Split(' ').Contains(CssClassSelector));
                List<TextBox> finalSet = new List<TextBox>();
                if (!String.IsNullOrEmpty(CssClassDeselector))
                    finalSet = selected.Where(c => !c.CssClass.Split(' ').Contains(CssClassDeselector)).ToList();
                else finalSet = selected.ToList();
                
                Targets.Clear();
                Targets.AddRange(finalSet.Select(tb => new Target(tb.ID)));
                if (Mode == Mode.TextBoxes) Mode = Mode.Exact;
            }
            else if (!String.IsNullOrEmpty(CssClassDeselector))
            {
                Control[] targs = TargetControls;
                IEnumerable<Target> items = targs.OfType<TextBox>().Where(t => !t.CssClass.Split(' ').Contains(CssClassDeselector)).Select(tb => new Target(tb.ID));
                Targets.Clear();
                Targets.AddRange(items);
                if (Mode == Mode.TextBoxes) Mode = Mode.Exact;
            }
        }

        #endregion

        #region Private Support

        private void SetStringDefaultValues()
        {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this, new Attribute[1] { new DefaultValueAttribute("") });
            foreach (PropertyDescriptor prop in props)
                if (prop.PropertyType == typeof(string) && prop.GetValue(this) == null) prop.SetValue(this, "");
        }

        #endregion

        #endregion

        #region Properties

        #region Inherited

        #region JS Command Generation

        protected override string[] SettingsCommands
        {
            get 
            {
                List<string> s = new List<string>(base.SettingsCommands);
                if (Theme == Theme.Simple) s.AddRange(SimpleThemeCommands);
                else s.AddRange(AdvancedThemeCommands);
                return s.ToArray();
            }
        }

        private string[] SimpleThemeCommands
        {
            get { return new string[] { String.Format(Localization_en.StringCommandFormat, "theme", "simple") }; }
        }

        private string[] AdvancedThemeCommands
        {
            get
            {
                List<string> settings = new List<string>();
                // TinyMCE will render toolbars 1-3, so this will clear the toolbars and only display what is entered in the control.
                if (Toolbars.Count > 0 && Toolbars.Count < 3)
                    while (Toolbars.Count < 3) Toolbars.Add(new Toolbar());
                Array.ForEach<Setting>(Settings, s => settings.AddRange(s.Commands));
                return settings.ToArray();
            }
        }

        #endregion

        protected override string GzipInclude
        {
            get 
            {
                string outUri = "TinyMCE.ashx?module=GzipModule";
                string[] gzipThemes = (from t in FindControls<TinyMCE>() 
                                       where t.Gzip.Enabled 
                                       select t.Theme.ToString("G").ToLower()).Distinct().ToArray();
                outUri += "&themes=" + String.Join(",", gzipThemes);

                List<Plugin> plugins = Plugins;

                if (plugins != null && plugins.Count > 0)
                    outUri += "&plugins=" + String.Join(",", plugins.ConvertAll<string>(p => p.Name).ToArray());

                if (!String.IsNullOrEmpty(Languages))
                {
                    string[] langs = Languages.Split(',');
                    List<string> abbrevs = new List<string>();
                    foreach (string s in langs)
                    {
                        string[] parts = s.Split('=');
                        if (parts.Length == 2) abbrevs.Add(parts[1]);
                        else abbrevs.Add(s);
                    }
                    outUri += "&languages=" + String.Join(",", abbrevs.ToArray());
                }
                return outUri;
            }
        }

        #endregion

        #region Plugin Architecture

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [SettingInfo]
        public virtual List<Plugin> Plugins
        {
            get { return (!DesignMode) ? PluginsInUse : ShippedPlugins; }
        }

        private List<Plugin> _installedPlugins;
        protected List<Plugin> InstalledPlugins
        {
            get
            {
                if (!DesignMode && _installedPlugins == null)
                {
                    string pluginDir = HttpContext.Current.Server.MapPath(VirtualPathUtility.AppendTrailingSlash(InstallPath) + "plugins\\");
                    if (!Directory.Exists(pluginDir))
                        throw new FileNotFoundException("Plugin directory not found.", pluginDir);
                    DirectoryInfo plugins = new DirectoryInfo(pluginDir);
                    string[] installedPlugins = Array.ConvertAll<DirectoryInfo, string>(plugins.GetDirectories(), d => d.Name);
                    _installedPlugins = (from s in ShippedPlugins where Array.Find<string>(installedPlugins, i => i == s.Name) != null select s).ToList();
                }
                return _installedPlugins;
            }
        }

        protected List<Plugin> PluginsInUse
        {
            get
            {
                List<string> buttons = new List<string>();

                // Get all distinct buttons in use
                Toolbars.ForEach(t => buttons.AddRange(t.Items.Split(',')));
                buttons = buttons.Distinct().ToList();

                List<Plugin> plugs = (from i in InstalledPlugins
                        from b in buttons
                        where i.Buttons.Contains(b)
                        select i
                        ).ToList();
                if(!String.IsNullOrEmpty(CustomPlugins))
                    plugs.AddRange(Array.ConvertAll<string, Plugin>(CustomPlugins.Split(','), s => new Plugin(s)));
                return plugs;
            }
        }

        [Category("Plugins"), Description("Add custom plugins by their name. They will be referenced under the TinyMCE reference plugin path.")]
        public string CustomPlugins { get; set; }

        #endregion

        #region General

        private Theme _theme = Theme.Advanced;
        [SettingInfo("theme", Render = RenderProperty.Always)]
        [DefaultValue(Theme.Advanced), NotifyParentProperty(true)]
        [Category("Layout"), Description("Specifies the TinyMCE theme to use.")]
        public Theme Theme
        {
            get { return _theme; }
            set { _theme = value; }
        }

        private List<Toolbar> _toolbars = new List<Toolbar>();
        [SettingInfo("theme_advanced_buttons", MultiCommandCollection = true)]
        [PersistenceMode(PersistenceMode.InnerProperty), NotifyParentProperty(true)]
        [Category("Layout"), Description("Stores the toolbars which contain the buttons to add to the editor.")]
        public List<Toolbar> Toolbars
        {
            get { return _toolbars; }
            private set { _toolbars = value; }
        }

        [SettingInfo("theme_advanced_toolbar_location")]
        [DefaultValue(null)]
        [Category("Layout"), Description("Sets the vertical alignment of the toolbars")]
        public Vertical? ToolbarPosition { get; set; }

        [SettingInfo("theme_advanced_toolbar_align"), NotifyParentProperty(true)]
        [DefaultValue(null)]
        [Category("Layout"), Description("Sets the horizontal alignment of the toolbars.")]
        public Horizontal? ToolbarAlignment { get; set; }

        [SettingInfo("theme_advanced_statusbar_location"), NotifyParentProperty(true)]
        [DefaultValue(null)]
        [Category("Layout"), Description("Sets the vertical alignment of the status bar.")]
        public Vertical? StatusbarPosition { get; set; }

        private bool _sourceFormat = true;
        [SettingInfo("apply_source_formatting", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(true)]
        [Category("Behavior"), Description("Will output HTML code as indented and formatted rather than a more compact version.")]
        public bool SourceFormatting
        {
            get { return _sourceFormat; }
            set { _sourceFormat = value; }
        }

        private bool _resizeHoriz = true;
        [SettingInfo("theme_advanced_resize_horizontal", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(true)]
        [Category("Behavior"), Description("Enables vertical resizing of the editor.")]
        public bool ResizeHorizontal 
        {
            get { return _resizeHoriz; }
            set 
            {
                _resizeHoriz = value;
                if (!value) Resize = true;
            }
        }

        [SettingInfo("theme_advanced_resizing", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(false)]
        [Category("Behavior"), Description("Enables resizing of the editor in all directions.")]
        public bool Resize { get; set; }

        [SettingInfo("spellchecker_languages"), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Behavior"), Description("The languages which will be spell checked.")]
        public string Languages { get; set; }

        [SettingInfo("editor_selector", Format = "{0}: \"{1}\""), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Selection"), Description("Extends TextBoxes which contain the specified css class")]
        public string CssClassSelector { get; set; }

        [SettingInfo("editor_deselector", Format = "{0}: \"{1}\""), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Selection"), Description("Extends all TextBoxes included by the mode property, not including those with the specified css class.")]
        public string CssClassDeselector { get; set; }

        [SettingInfo("content_css", IsUri = true), NotifyParentProperty(true)]
        [DefaultValue("")]
        [UrlProperty]
        [Editor(typeof(UrlEditor), typeof(UITypeEditor))]
        [Category("Layout"), Description("Applies css styles via the specified file to the editor's contents.")]
        public string CssFile { get; set; }

        [SettingInfo("spellchecker_rpc_url", IsUri = true), NotifyParentProperty(true)]
        [DefaultValue("")]
        [UrlProperty]
        [Category("Behavior"), Description("The url which will do the spell checking. (A default module is included with this control.)")]
        public string SpellCheckerUrl { get; set; }

        [SettingInfo("plugin_insertdate_dateFormat", Format = "{0}: \"{1}\""), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Behavior"), Description("Specifies the format string when inserting the current date. (i.e., %m-%d-%Y)")]
        public string DateFormat { get; set; }

        [SettingInfo("plugin_insertdate_timeFormat", Format = "{0}: \"{1}\""), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Behavior"), Description("Specifies the format string when inserting the time. (i.e., %H:%M:%S)")]
        public string TimeFormat { get; set; }

        [SettingInfo("external_link_list_url", IsUri = true), NotifyParentProperty(true)]
        [DefaultValue("")]
        [UrlProperty]
        [Editor(typeof(UrlEditor), typeof(UITypeEditor))]
        [Category("Options"), Description("Specifies the javascript file with predefined links.")]
        public string LinkListUrl { get; set; }

        [SettingInfo("external_image_list_url", IsUri = true), NotifyParentProperty(true)]
        [DefaultValue("")]
        [UrlProperty]
        [Editor(typeof(UrlEditor), typeof(UITypeEditor))]
        [Category("Options"), Description("Specifies the javascript file with predefined image paths.")]
        public string ImageListUrl { get; set; }

        [SettingInfo("media_external_list_url", IsUri = true), NotifyParentProperty(true)]
        [DefaultValue("")]
        [UrlProperty]
        [Editor(typeof(UrlEditor), typeof(UITypeEditor))]
        [Category("Options"), Description("Specifies the javascript file with predefined media paths.")]
        public string MediaListUrl { get; set; }

        [SettingInfo("template_external_list_url", IsUri = true), NotifyParentProperty(true)]
        [DefaultValue("")]
        [UrlProperty]
        [Editor(typeof(UrlEditor), typeof(UITypeEditor))]
        [Category("Options"), Description("Specifies the javascript file with predefined template paths.")]
        public string TemplateListUrl { get; set; }

        [SettingInfo("theme_advanced_toolbar_location", Format = "{0}: \"external\""), NotifyParentProperty(true)]
        [DefaultValue(false)]
        [Category("Behavior"), Description("Specifies whether the editor will pop up when it is highlighted or not.")]
        public bool IsPopupToolbar { get; set; }

        [SettingInfo("tab_focus", Format = "{0}: \":prev,:next\""), NotifyParentProperty(true)]
        [DefaultValue(false)]
        [Category("Behavior"), Description("When tabbing, the editor will neglect buttons and other elements only tab to the editing surface.")]
        public bool TabFocus { get; set; }

        [SettingInfo("ask", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(false)]
        [Category("Behavior"), Description("Will ask the client whether they want to use the editor or not.")]
        public bool Ask { get; set; }

        [SettingInfo("valid_elements"), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Behavior"), Description("Defines which elements will remain in the edited text when the editor saves.")]
        public string ValidElements { get; set; }

        [SettingInfo("setup", Format = "{0}: \"{1}\""), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Behavior"), Description("Enables you to add events to editor instances before they get rendered")]
        public string Setup { get; set; }

        [SettingInfo("theme_advanced_styles"), NotifyParentProperty(true)]
        [DefaultValue("")]
        [Category("Options"), Description("A semicolon separated list of class titles and class names separated by = which will be presented to the user in the styles dropdown list and the class names will be inserted.")]
        public string Styles { get; set; }

        [SettingInfo("entity_encoding", Format = "{0}: \"raw\""), NotifyParentProperty(true)]
        [DefaultValue(false)]
        [Category("Behavior"), Description("Controls how entities/characters get processed.")]
        public bool IsEntityEncoding { get; set; }

        private bool _addUnloadTrigger = true;
        [SettingInfo("add_unload_trigger", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(true)]
        [Category("Behavior"), Description("Triggers the supplied method during the unload process.")]
        public bool AddUnloadTrigger { get { return _addUnloadTrigger; } set { _addUnloadTrigger = value; } }

        private bool _removeLineBreaks = true;
        [SettingInfo("remove_linebreaks", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(true)]
        [Category("Behavior"), Description("Controls if linebreak characters should be removed from output HTML or not.")]
        public bool RemoveLinebreaks { get { return _removeLineBreaks; } set { _removeLineBreaks = value; } }

        private bool _inlineStyles = true;
        [SettingInfo("inline_styles", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(true)]
        [Category("Behavior"), Description("This will make the editor output more XHTML strict compatible.")]
        public bool InlineStyles { get { return _inlineStyles; } set { _inlineStyles = value; } }

        private bool _convertFontToSpan = true;
        [SettingInfo("convert_fonts_to_spans", Format = "{0}: {1:L}"), NotifyParentProperty(true)]
        [DefaultValue(true)]
        [Category("Behavior"), Description("Convert all font elements to span elements and generate span elements instead of font elements.")]
        public bool ConvertFontToSpan { get { return _convertFontToSpan; } set { _convertFontToSpan = value; } }

        #endregion

        #endregion
    }
}